﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Linq;
using System.Collections.Generic;
using PatternLockControl.Code;
using System.Collections.ObjectModel;

namespace PatternLockControl
{
    public partial class PatternLockMetroControl : UserControl
    {
        #region Data
        public Grid RootContainer { get; set; }
        /// <summary>
        /// Pattern drawing on screen
        /// </summary>
        public IDictionary<Point, SquarePattern> MyPattern = new Dictionary<Point, SquarePattern>();

        #region LockPatterShapeProperty
        private ObservableCollection<SquarePattern> LockPatterShapes;
        #endregion

        /// <summary>
        /// Selected line brush 
        /// </summary>
        private static readonly SolidColorBrush SelectedLineBrush = new SolidColorBrush { Color = Colors.White };

        /// <summary>
        /// Represent the line drawing by your finger
        /// </summary>
        private Line _MouseMoveLine;

        /// <summary>
        /// Registered pattern
        /// </summary>
        private IDictionary<Point, SquarePattern> _myRegisteredPattern;

        /// <summary>
        /// Register mode
        /// </summary>
        public bool IsRegisterationMode
        {
            get { return (bool)GetValue(IsRegisterationModeProperty); }
            set { SetValue(IsRegisterationModeProperty, value); }
        }

        // Using a DependencyProperty as the backing store for IsRegisterationMode.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsRegisterationModeProperty =
            DependencyProperty.Register("IsRegisterationMode", typeof(bool), typeof(PatternLockMetroControl), new PropertyMetadata(true));



        public double DottedLineWidth { get { return 5; } }
        public double DragLineWidth { get { return 7; } }

        //this is required to make sure latest line is drawn over others
        public int ZIdxForDrawnLines { get; set; }

        public List<int> PatternToMatch { get; set; }

        public List<int> PatternToMatchDecoy { get; set; }

        /// <summary>
        /// This even will only be triggered when lock control is in Registration Mode
        /// </summary>

        public event RegistrationSuccessEventHandler RegistrationSuccess;
        public delegate void RegistrationSuccessEventHandler(object sender, RegistrationSuccessEventArg e);

        /// <summary>
        /// This even will only be triggered when lock control is in MatchPattern Mode
        /// </summary>
        public event PatternMatchSuccessEventHandler PatternMatchSuccess;
        public delegate void PatternMatchSuccessEventHandler(object sender, PatternMatchSuccessEventArg e);
        #endregion

        public PatternLockMetroControl()
        {
            // Required to initialize variables
            InitializeComponent();
            this.Loaded += PatternLockMetroControl_Loaded;
            this.Unloaded += PatternLockMetroControl_Unloaded;
            this.RootContainer = this.ContentPanel;
            this.tbxMessage.Text = "Please swipe to draw your pattern";
        }

        void PatternLockMetroControl_Unloaded(object sender, RoutedEventArgs e)
        {
            var count = RootContainer.Children.Count;
            for (int i = 0; i < count; i++)
            {
                var sq = RootContainer.Children[0] as Border;
                sq.MouseMove -= SquarePattern_MouseMoveEvent;
                sq.MouseLeftButtonUp -= Square_MouseLeftButtonUp;
                RootContainer.Children.RemoveAt(0);
            }
        }

        void PatternLockMetroControl_Loaded(object sender, RoutedEventArgs e)
        {
            InitializeAnimateGrid();
        }

        /// <summary>
        /// Initialize animate grid
        /// </summary>
        private void InitializeAnimateGrid()
        {
            LockPatterShapes = new ObservableCollection<SquarePattern>();
            var chromeColor = (this.Resources["PhoneAccentBrush"] as SolidColorBrush).Color;
            int pattVal = 0;
            for (int row = 0, maxRows = RootContainer.RowDefinitions.Count; row < maxRows; ++row)
            {
                for (int col = 0, maxCols = RootContainer.ColumnDefinitions.Count; col < maxCols; ++col)
                {
                    var LockPatterShape = new SquarePattern(row, col, 100, pattVal++, Colors.Black, chromeColor, chromeColor);
                    LockPatterShape.GridPosition(row, col);
                    RootContainer.Children.Add(LockPatterShape.Square);
                    LockPatterShape.Square.MouseEnter += SquarePattern_MouseMoveEvent;
                    LockPatterShape.Square.MouseLeftButtonUp += Square_MouseLeftButtonUp;
                    LockPatterShapes.Add(LockPatterShape);
                }
            }
        }

        void Square_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            InitializeScreen();
        }

        public void SquarePattern_MouseMoveEvent(object sender, System.Windows.Input.MouseEventArgs e)
        {
            Border borderSq = (sender as Border);
            KeyValuePair<Point, SquarePattern> ptSquareTouch = MyPattern.SingleOrDefault(c => ReferenceEquals(c.Value.Square, borderSq));
            if (ptSquareTouch.Value == null)
            {
                SquarePattern sqPatt = LockPatterShapes.SingleOrDefault(c => ReferenceEquals(c.Square, borderSq));
                Point point = borderSq.TransformToVisual(RootContainer).Transform(new Point(borderSq.Width / 2, borderSq.Width / 2));
                ptSquareTouch = new KeyValuePair<Point, SquarePattern>(point, sqPatt);
                ptSquareTouch.Value.Square.Opacity = 1.0;
                ptSquareTouch.Value.MarkAsSelected();
                MyPattern.Add(ptSquareTouch);
                DrawLine();
            }
        }

        /// <summary>
        /// Draw a line between two square objects
        /// </summary>
        private void DrawLine()
        {
            if (MyPattern.Count < 2) return;
            if (_MouseMoveLine != null)
            {
                RootContainer.Children.Remove(_MouseMoveLine);
                _MouseMoveLine = null;
            }

            Point ptDest, ptSource;
            var sqPattEnd = MyPattern.LastOrDefault().Value;
            var sqPattStart = MyPattern.ElementAt(MyPattern.Count - 2).Value;

            //Find points to draw line from start and end.
            ptDest = LineHelper.GetPtRelativeToQuadrant(RootContainer, sqPattEnd.Square, LineHelper.FindQuadrantToDrawLineBW(sqPattEnd.Row, sqPattEnd.Column, sqPattStart.Row, sqPattStart.Column));
            ptSource = LineHelper.GetPtRelativeToQuadrant(RootContainer, sqPattStart.Square, LineHelper.FindQuadrantToDrawLineBW(sqPattStart.Row, sqPattStart.Column, sqPattEnd.Row, sqPattEnd.Column));

            Line line;
            LineType lType = LineHelper.FindLineType(sqPattStart.Row, sqPattStart.Column, sqPattEnd.Row, sqPattEnd.Column);

            if (lType == LineType.SIMPLE)
                line = LineHelper.CreateLine(ptSource, ptDest, DragLineWidth, (this.Resources["PhoneAccentBrush"] as SolidColorBrush), ZIdxForDrawnLines++, RootContainer, lType);
            else
                line = LineHelper.CreateLine(ptSource, ptDest, DottedLineWidth, SelectedLineBrush, ZIdxForDrawnLines++, RootContainer, lType);
            AddPolygon(MyPattern.ElementAt(MyPattern.Count - 2).Value.Square, LineHelper.FindQuadrantToDrawLineBW(sqPattStart.Row, sqPattStart.Column, sqPattEnd.Row, sqPattEnd.Column));

            RootContainer.Children.Add(line);
        }

        private void AddPolygon(Border shape, LineQuadrant quad)
        {
            Polygon poly = new Polygon();
            poly.Fill = new SolidColorBrush(Colors.White);
            poly.Points = LineHelper.GetPointsForPolygon(shape.ActualWidth, quad, poly);
            shape.BorderThickness = new Thickness(0);
            shape.Child = poly;
            //Set the Max ZIdx for poly/Border so that it will always be on top of all drawn lines
            shape.SetValue(Canvas.ZIndexProperty, ZIdxForDrawnLines + 1);
        }

        /// <summary>
        /// Mouse move
        /// </summary>
        void UserControl_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
        {
            if (MyPattern.Count < 1)
                return;

            KeyValuePair<Point, SquarePattern> data = MyPattern.LastOrDefault();
            Point from = data.Key;
            Point to = e.GetPosition(RootContainer);

            if (null == _MouseMoveLine)
                // Set always to one higher ZIdx so that drawing line will always be at top
                _MouseMoveLine = LineHelper.CreateLine(from, to, DragLineWidth, SelectedLineBrush, ZIdxForDrawnLines + 1, RootContainer, LineType.SIMPLE);
            else
                _MouseMoveLine.UpdateLine(from, to);

            if (!RootContainer.Children.Contains(_MouseMoveLine))
                RootContainer.Children.Add(_MouseMoveLine);
        }

        /// <summary>
        /// Control register mode 
        /// </summary>
        private void ControlRegisterMode()
        {
            if (IsRegisterationMode && !MyPattern.Any())
            {
                //MessageBox.Show("Create your pattern, please!");
                this.tbxMessage.Text = "Please swipe to draw your pattern";
                return;
            }
            if (IsRegisterationMode)
            {
                if (_myRegisteredPattern == null) // first time swipe for registration 
                {
                    _myRegisteredPattern = new Dictionary<Point, SquarePattern>(MyPattern);
                    this.tbxMessage.Text = "Swipe again to confirm your pattern";
                }
                else if (null != _myRegisteredPattern && _myRegisteredPattern.EqualWithOrder(MyPattern))
                {
                    this.tbxMessage.Text = "Great! Password Successfully registered!";
                    RegistrationSuccess(this, new RegistrationSuccessEventArg(GetRegisteredPattern(MyPattern)));
                }
                else
                {
                    _myRegisteredPattern = null;
                    this.tbxMessage.Text = "Start over. Draw a new pattern";
                }
            }
            else
            {
                if (!MyPattern.Any())
                    return;

                if (PatternToMatch != null && PatternToMatch.SequenceEqual(GetRegisteredPattern(MyPattern)) || PatternToMatchDecoy != null && PatternToMatchDecoy.SequenceEqual(GetRegisteredPattern(MyPattern)))
                {
                    PatternMatchSuccess(this, new PatternMatchSuccessEventArg(true, GetRegisteredPattern(MyPattern)));
                }
                else
                    this.tbxMessage.Text = "Pattern did not match :( please try again";

                //if (null != _myRegisteredPattern && _myRegisteredPattern.EqualWithOrder(MyPattern))
                //    MessageBox.Show("Unlock");
                //else
                //    MessageBox.Show("Wrong pattern ! try again !");
            }
        }

        private List<int> GetRegisteredPattern(IDictionary<Point, SquarePattern> MyPattern)
        {
            List<int> regPattern = new List<int>();
            foreach (var patt in MyPattern)
            {
                regPattern.Add(patt.Value.Value);
            }
            return regPattern;
        }

        /// <summary>
        /// Initialize screen
        /// </summary>
        private void InitializeScreen()
        {
            //Give the original stroke 
            foreach (Border border in RootContainer.Children.OfType<Border>())
            {
                border.Background = new SolidColorBrush(Colors.Black);
            }
            // Delete all lines shape 
            var uiElements = new Stack<UIElement>(RootContainer.Children.Where(elt => elt is Line));
            while (0 != uiElements.Count)
            {
                RootContainer.Children.Remove(uiElements.Pop());
            }
            //Remove all polygon items
            foreach (var item in MyPattern)
            {
                if (item.Value.Square.Child != null)
                {
                    item.Value.Square.Child = null;
                    item.Value.Square.BorderThickness = new Thickness(2);
                }
            }
            MyPattern.Clear();

            //Reset counter for  ZIdxForDrawnLines.
            ZIdxForDrawnLines = 0;
        }

        private void UserControl_ManipulationCompleted(object sender, System.Windows.Input.ManipulationCompletedEventArgs e)
        {
            ControlRegisterMode();
            InitializeScreen();
        }

        //Manage the width and height of this control here because in loaded event inner content panel's height can't be set back
        //when control is already in collapsed mode. 
        private void UserControl_SizeChanged(object sender, System.Windows.SizeChangedEventArgs e)
        {
            RootContainer.Height = RootContainer.ActualWidth;
        }
    }
}